package com.hwtx.form.domain.handle;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.core.type.TypeReference;
import com.google.common.collect.Lists;
import com.hwtx.form.annotation.*;
import com.hwtx.form.domain.FormConstants;
import com.hwtx.form.domain.def.FormDef;
import com.hwtx.form.domain.handle.datamodel.DataModelDetail;
import com.hwtx.form.domain.repo.ApiRepo;
import com.hwtx.form.domain.repo.DataModelRepo;
import com.hwtx.form.domain.vo.*;
import com.hwtx.form.dto.FormListQuery;
import com.hwtx.form.persistence.entity.ApiDef;
import com.hwtx.form.persistence.entity.ApiDefField;
import com.hwtx.form.query.FormValueQuery;
import com.hwtx.form.util.FormUtil;
import com.hwtx.form.util.JacksonUtil;
import com.hwtx.form.util.Param;
import io.geekidea.boot.auth.util.LoginUtil;
import io.geekidea.boot.framework.exception.BusinessException;
import io.geekidea.boot.framework.page.Paging;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.stream.Streams;
import org.springframework.transaction.annotation.Transactional;

import java.lang.reflect.Field;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.hwtx.form.domain.FormConstants.API_HANDLE_NAME;
import static com.hwtx.form.domain.FormConstants.API_PUBLISH;
import static com.hwtx.form.util.FormUtil.*;
import static com.hwtx.form.util.JacksonUtil.map2Oject;
import static com.hwtx.form.util.StrUtil.toCamelCase;

@FormType(value = API_HANDLE_NAME, comment = "api管理")
@Slf4j
public class ApiConfigDataHandle implements FormDataHandle {

    @Resource
    private ApiRepo apiRepo;
    @Resource
    private DataModelRepo dataModelRepo;

    @Action
    @Log(type = OperationType.UPDATE, value = "发布API")
    public boolean publish(Map<String, Object> formData) {
        String id = FormUtil.getStrPostValue(formData, "id");
        if (StringUtils.isBlank(id)) {
            throw new BusinessException("id参数错误");
        }
        ApiDef apiDef = apiRepo.getById(Long.valueOf(id));
        if (apiDef == null) {
            throw new BusinessException("api不存在");
        }
        apiDef.setStatus(API_PUBLISH);
        return apiRepo.updateById(apiDef);
    }

    @Override
    public Paging<?> page(FormDef formDef, FormListQuery formListQuery) {
        String type = FormUtil.getStrPostValue(formListQuery.getSearchPostData(), "type");
        String code = FormUtil.getStrPostValue(formListQuery.getSearchPostData(), "code");
        List<String> authType = FormUtil.getListStrPostValue(formListQuery.getSearchPostData(), "authType");
        Page<ApiDef> page = new Page<>(formListQuery.getPageIndex(), formListQuery.getPageSize());
        LambdaQueryWrapper<ApiDef> wrapper = new LambdaQueryWrapper<>();
        if (StringUtils.isNotBlank(type)) {
            wrapper.eq(ApiDef::getType, type);
        }
        if (StringUtils.isNotBlank(code)) {
            wrapper.eq(ApiDef::getCode, code);
        }
        if (CollectionUtils.isNotEmpty(authType)) {
            if (authType.contains("2")) {
                wrapper.eq(ApiDef::getAdminAuth, 1);
            }
            if (authType.contains("1")) {
                wrapper.eq(ApiDef::getAppAuth, 1);
            }
        }
        wrapper.orderBy(true, false, ApiDef::getUpdateTime);
        Page<ApiDef> result = apiRepo.page(page, wrapper);
        Paging<ApiDef> ret = Paging.of(result.getRecords(), result.getTotal(), formListQuery.getPageIndex(), formListQuery.getPageSize());
        ret.setUData(Map.of("canEdit", LoginUtil.isAdmin()));
        return ret;
    }

    @Override
    public FormListVo<?> query(FormDef formDef, FormListQuery formListQuery) {
        String code = FormUtil.getStrPostValue(formListQuery.getSearchPostData(), "code");
        Map<Long, ApiDefField> fields = apiRepo.getApiDefFields(ApiRepo.ApiDefFieldQuery.builder().apiCode(code).build())
                .stream().collect(Collectors.toMap(ApiDefField::getModelDetailId, Function.identity()));
        List<ApiDetailVo> ret = modelDetails(formListQuery, fields);
        FormListVo<ApiDetailVo> formListVo = new FormListVo<>();
        formListVo.setRows(ret);
        formListVo.setData(fields.keySet());
        return formListVo;
    }

    private List<ApiDetailVo> modelDetails(FormListQuery formListQuery, Map<Long, ApiDefField> fields) {
        String modelName = FormUtil.getStrPostValue(formListQuery.getSearchPostData(), "modelName");
        if (StringUtils.isBlank(modelName)) {
            throw new BusinessException("modelName参数错误");
        }
        List<DataModelDetail> dataModels = dataModelRepo.getDetails(modelName, FormConstants.Status.VALID);
        return dataModels.stream().map(item -> {
            boolean checked = fields.containsKey(item.getId());
            ApiDetailVo apiDetailVo = new ApiDetailVo();
            apiDetailVo.setId(item.getId());
            apiDetailVo.setTableName(item.getTableName());
            apiDetailVo.setNum(!checked ? item.getNum() : fields.get(item.getId()).getNum());
            apiDetailVo.setField(item.getName());
            apiDetailVo.setName(toCamelCase(item.getName()));
            apiDetailVo.setDescription(item.getDescription());
            apiDetailVo.setChecked(checked);
            return apiDetailVo;
        }).toList();
    }

    @Override
    public FormRepData getFormData(FormDef formDef, FormValueQuery formValueQuery) {
        Long queryId = formValueQuery.getValueIds().getFirst();
        if (Objects.equals(formValueQuery.getType(), "detail")) {
            ApiDefField apiDefField = apiRepo.getApiDefField(ApiRepo.ApiDefFieldQuery.builder().id(queryId).build());
            return FormRepData.builder().data(apiDefField).build();
        }
        ApiDef apiDef = apiRepo.getById(queryId);
        StringBuilder sb = new StringBuilder();
        if (Objects.equals(apiDef.getAppAuth(), 1)) {
            sb.append("1").append(",");
        }
        if (Objects.equals(apiDef.getAdminAuth(), 1)) {
            sb.append("2");
        }
        apiDef.setAdminAuth(null);
        apiDef.setAppAuth(null);
        Map<String, Object> ret = JacksonUtil.object2Map(apiDef);
        ret.put("authType", sb.toString());
        if (StringUtils.isNotBlank(apiDef.getParams())) {
            List<Param> params = JacksonUtil.json2Array(apiDef.getParams(), new TypeReference<>() {
            });
            if (CollectionUtils.isNotEmpty(params)) {
                params = params.stream().filter(param -> !param.isEmpty()).peek(param -> param.setCanEdit(StringUtils.isEmpty(apiDef.getExt()))).toList();
                if (CollectionUtils.isNotEmpty(params)) {
                    ret.put("params", params);
                } else {
                    ret.remove("params");
                }
            }
        }
        if (ret.get("params") == null && apiDef.getExt() != null) {
            Action action = FormUtil.getMethodAction(apiDef);
            if (action != null) {
                ret.put("params", Arrays.stream(action.inputs()).map(input -> {
                    Param param = new Param();
                    param.setName(input.name());
                    param.setRemark(input.comment());
                    param.setCanEdit(false);
                    return param;
                }).toList());
            }
        }
        return FormRepData.builder().data(ret).build();
    }

    @Override
    @Transactional
    public Object removeFormData(FormDef formDef, FormValueQuery formValueQuery) {
        List<String> apiCodes = Lists.newArrayList();
        if (!LoginUtil.isAdmin()) {
            formValueQuery.getValueIds().forEach(id -> {
                ApiDef exist = apiRepo.getById(id);
                if (exist.getStatus() == 2) {
                    throw new BusinessException("API:" + exist.getCode() + "已发布，无法修改");
                }
                apiCodes.add(exist.getCode());
            });
        }

        if (Objects.equals(formValueQuery.getType(), "detail")) {
            return true;
        }
        apiRepo.removeBatchByIds(formValueQuery.getValueIds());
        apiCodes.forEach(code -> apiRepo.removeApiFields(ApiRepo.ApiDefFieldQuery.builder().apiCode(code).build()));
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Object saveFormData(FormDef formDef, FormData formData) {

        ApiDef apiDef = map2Oject(formData.getData(), ApiDef.class);
        if (apiDef == null) {
            throw new BusinessException("参数错误");
        }
        if (!LoginUtil.isAdmin() && apiDef.getId() != null) {
            ApiDef exist = apiRepo.getById(apiDef.getId());
            if (exist.getStatus() == 2) {
                throw new BusinessException("该API已发布，无法修改");
            }
        }
        String type = getStrPostValue(formData.getData(), "ttype");
        if (StringUtils.isBlank(type)) {
            throw new BusinessException("未知类型,请确认type参数");
        }
        if (Objects.equals("detail", type)) {
            return saveDetailForModel(formData);
        }
        String authType = getStrPostValue(formData.getData(), "authType");
        if (!Objects.equals(type, "quick")) {
            if (StringUtils.isNotBlank(authType)) {
                if (authType.contains("1") && authType.contains("2")) {
                    apiDef.setAdminAuth(1);
                    apiDef.setAppAuth(1);
                } else if (authType.contains("2") && !authType.contains("1")) {
                    apiDef.setAdminAuth(1);
                    apiDef.setAppAuth(0);
                } else if (!authType.contains("2") && authType.contains("1")) {
                    apiDef.setAdminAuth(0);
                    apiDef.setAppAuth(1);
                }
            } else {
                apiDef.setAppAuth(0);
                apiDef.setAdminAuth(0);
            }
        }
        checkParamsAndRetValue(apiDef);
        checkApiCodeAndFields(apiDef);
        if (apiDef.getId() == null) {
            ApiDef exist = apiRepo.getOne(new LambdaQueryWrapper<ApiDef>().eq(ApiDef::getCode, apiDef.getCode()));
            if (exist != null) {
                throw new RuntimeException("api编码已存在");
            }
            return apiRepo.save(apiDef);
        } else {
            if (Objects.equals(type, "quick")) {
                ApiDef exist = apiRepo.getById(apiDef.getId());
                exist.setAdminAuth(apiDef.getAdminAuth());
                exist.setAppAuth(apiDef.getAppAuth());
                return apiRepo.updateById(exist);
            }
            return apiRepo.updateById(apiDef);
        }
    }

    private boolean saveDetailForModel(FormData formData) {
        List<Long> ids = getListLongByStrValue(formData.getData(), "ids");
        String code = getStrPostValue(formData.getData(), "code");
        if (StringUtils.isBlank(code)) {
            throw new BusinessException("code参数错误");
        }
        String name = getStrPostValue(formData.getData(), "name");
        if (StringUtils.isNotBlank(name)) {
            String id = getStrPostValue(formData.getData(), "id");
            if (StringUtils.isBlank(id)) {
                return false;
            }
            ApiDefField apiDefField = apiRepo.getApiDefField(ApiRepo.ApiDefFieldQuery.builder().modelDetailId(Long.valueOf(id)).apiCode(code).build());
            if (apiDefField == null) {
                throw new BusinessException("无法修改");
            }
            apiDefField.setName(name);
            apiDefField.setNum(getIntPostValue(formData.getData(), "num"));
            apiDefField.setUpdateId(LoginUtil.getUsername());
            apiDefField.setUpdateTime(new Date());
            return apiRepo.updateById(apiDefField);
        }
        if (CollectionUtils.isEmpty(ids)) {
            throw new BusinessException("ids参数错误");
        }
        List<Long> existModelIdFields = apiRepo.getApiDefFields(ApiRepo.ApiDefFieldQuery.builder().apiCode(code).build()).stream().map(ApiDefField::getModelDetailId).toList();
        List<DataModelDetail> dataModelDetails = dataModelRepo.getDetails(DataModelDetailQuery.builder().ids(ids).build());
        Map<Long, DataModelDetail> detailsMap = dataModelDetails.stream().collect(Collectors.toMap(DataModelDetail::getId, Function.identity()));
        Collection<Long> addIds = CollectionUtils.subtract(ids, existModelIdFields);
        Collection<Long> delIds = CollectionUtils.subtract(existModelIdFields, ids);

        addIds.forEach(addId -> {
            DataModelDetail dataModelDetail = detailsMap.get(addId);
            if (dataModelDetail != null) {
                ApiDefField insertIt = new ApiDefField();
                insertIt.setModelDetailId(addId);
                insertIt.setTableName(dataModelDetail.getTableName());
                insertIt.setApiCode(code);
                insertIt.setName(toCamelCase(dataModelDetail.getName()));
                insertIt.setDescription(dataModelDetail.getDescription());
                insertIt.setNum(dataModelDetail.getNum());
                insertIt.setCreateId(LoginUtil.getUsername());
                insertIt.setCreateTime(new Date());
                insertIt.setUpdateId(LoginUtil.getUsername());
                insertIt.setUpdateTime(new Date());
                apiRepo.saveDetails(insertIt);
            }
        });
        delIds.forEach(delId -> apiRepo.removeApiFields(ApiRepo.ApiDefFieldQuery.builder().modelDetailId(delId).apiCode(code).build()));
        return true;
    }

    private void checkApiCodeAndFields(ApiDef apiDef) {
        if (StringUtils.isEmpty(apiDef.getExt()) && StringUtils.isEmpty(apiDef.getModelName())) {
            throw new BusinessException("请选择模型或扩展接口");
        }
        if (StringUtils.isNotEmpty(apiDef.getExt()) && StringUtils.isNotEmpty(apiDef.getModelName())) {
            throw new BusinessException("扩展接口和模型不可同时使用");
        }
        if (StringUtils.isNotEmpty(apiDef.getExt())) {
            apiRepo.removeApiFields(ApiRepo.ApiDefFieldQuery.builder().apiCode(apiDef.getCode()).build());
        }
        if (StringUtils.isNotEmpty(apiDef.getModelName())) {
            Set<Long> dataModelDetailIds = apiRepo.getApiDefFields(ApiRepo.ApiDefFieldQuery.builder().apiCode(apiDef.getCode()).build()).stream().map(ApiDefField::getModelDetailId).collect(Collectors.toSet());
            if (!dataModelDetailIds.isEmpty()) {
                Set<Long> allDataModelIds = dataModelRepo.getDetails(DataModelDetailQuery.builder().modelName(apiDef.getModelName()).build()).stream().map(DataModelDetail::getId).collect(Collectors.toSet());
                if (!CollectionUtils.isSubCollection(dataModelDetailIds, allDataModelIds)) {
                    throw new BusinessException("字段信息与模型字段不一致");
                }
            }
        }
    }

    private void checkParamsAndRetValue(ApiDef apiDef) {

        if (StringUtils.isNotEmpty(apiDef.getModelName())) {
            if (apiRepo.getApiDefFields(ApiRepo.ApiDefFieldQuery.builder().apiCode(apiDef.getCode()).build()).isEmpty()) {
                throw new BusinessException("未添加返回字段");
            }
        }
        List<Param> params = null;
        if (StringUtils.isNotEmpty(apiDef.getParams())) {
            params = JacksonUtil.json2Array(apiDef.getParams(), new TypeReference<>() {
            });
            Set<String> fields = Streams.of(ApiDef.class.getDeclaredFields()).map(Field::getName).collect(Collectors.toSet());
            params.forEach(param -> {
                if (fields.contains(param.getName())) {
                    throw new BusinessException("参数字段【" + param.getName() + "】不合法，为保留字段");
                }
            });
        }

        if (apiDef.getExt() != null) {
            Action action = FormUtil.getMethodAction(apiDef);
            if (action != null) {
                if (ArrayUtils.isNotEmpty(action.inputs()) && StringUtils.isBlank(apiDef.getParams())) {
                    throw new BusinessException("请填配置参数");
                }
                if (params != null) {
                    Set<String> paramNames = params.stream().map(Param::getName).collect(Collectors.toSet());
                    for (Parameter parameter : action.inputs()) {
                        if (!paramNames.contains(parameter.name())) {
                            throw new BusinessException("未包含参数:" + parameter.name());
                        }
                    }
                }
            }
        }
    }
}